package cn.kmsoft.spatialboot.config;

import cn.kmsoft.spatialboot.config.dbswitch.DataSourceAdvice;
import cn.kmsoft.spatialboot.config.dbswitch.DynamicDataSource;
import com.alibaba.druid.filter.logging.Log4jFilter;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.aop.PointcutAdvisor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import java.sql.SQLException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Configuration
@ConditionalOnExpression("!'${dbconfig}'.isEmpty()")
@ConfigurationProperties(prefix = "dbconfig")
public class DbConfig {
    private List<String> dbKeys;
    private List<DbType> dbTypes;
    private List<JdbcConfig> jdbcConfigs;


    //属性类型是字符串，通过别名的方式配置扩展插件，常用的插件有：监控统计用的filter:stat 日志用的filter:log4j 防御sql注入的filter:wall
    private String filters = "stat";
    //最大连接池数量
    private Integer maxActive = 40;
    //初始化时建立物理连接的个数。初始化发生在显示调用init方法，或者第一次getConnection时
    private Integer initialSize = 5;
    //最小连接池数量
    private Integer minIdle = 5;
    //是否缓存preparedStatement，也就是PSCache。PSCache对支持游标的数据库性能提升巨大，比如说oracle。在mysql下建议关闭。
    private Boolean poolPreparedStatements = true;
    //要启用PSCache，必须配置大于0，当大于0时，poolPreparedStatements自动触发修改为true。
    //在Druid中，不会存在Oracle下PSCache占用内存过多的问题，可以把这个数值配置大一些，比如说100
    private Integer maxOpenPreparedStatements = 40;
    //获取连接时最大等待时间，单位毫秒。配置了maxWait之后，缺省启用公平锁，并发效率会有所下降，
    //如果需要可以通过配置useUnfairLock属性为true使用非公平锁。
    private Integer maxWait = 60000;

    //有两个含义：
    //1) Destroy线程会检测连接的间隔时间 2) testWhileIdle的判断依据，详细看testWhileIdle属性的说明
    private Long timeBetweenEvictionRunsMillis = 60000L;
    private Long minEvictableIdleTimeMillis = 30000L;
    //用来检测连接是否有效的sql，要求是一个查询语句。如果validationQuery为null，
    //testOnBorrow、testOnReturn、testWhileIdle都不会起作用。
    private String validationQuery = "SELECT 1 from dual";
    //建议配置为true，不影响性能，并且保证安全性。申请连接的时候检测，如果空闲时间大于timeBetweenEvictionRunsMillis，
    //执行validationQuery检测连接是否有效。
    private Boolean testWhileIdle = true;
    //申请连接时执行validationQuery检测连接是否有效，做了这个配置会降低性能。
    private Boolean testOnBorrow = false;
    //归还连接时执行validationQuery检测连接是否有效，做了这个配置会降低性能
    private Boolean testOnReturn = false;

    public List<String> getDbKeys() {
        return dbKeys;
    }
    public void setDbKeys(List<String> dbKeys) {
        this.dbKeys = dbKeys;
    }

    public List<DbType> getDbTypes() {
        return dbTypes;
    }
    public void setDbTypes(List<DbType> dbTypes) {
        this.dbTypes = dbTypes;
    }

    public List<JdbcConfig> getJdbcConfigs() {
        return jdbcConfigs;
    }
    public void setJdbcConfigs(List<JdbcConfig> jdbcConfigs) {
        this.jdbcConfigs = jdbcConfigs;
    }


    public String getFilters() {
        return filters;
    }
    public void setFilters(String filters) {
        this.filters = filters;
    }

    public Integer getMaxActive() {
        return maxActive;
    }
    public void setMaxActive(Integer maxActive) {
        this.maxActive = maxActive;
    }

    public Integer getInitialSize() {
        return initialSize;
    }
    public void setInitialSize(Integer initialSize) {
        this.initialSize = initialSize;
    }

    public Integer getMinIdle() {
        return minIdle;
    }
    public void setMinIdle(Integer minIdle) {
        this.minIdle = minIdle;
    }

    public Boolean getPoolPreparedStatements() {
        return poolPreparedStatements;
    }
    public void setPoolPreparedStatements(Boolean poolPreparedStatements) {
        this.poolPreparedStatements = poolPreparedStatements;
    }

    public Integer getMaxOpenPreparedStatements() {
        return maxOpenPreparedStatements;
    }
    public void setMaxOpenPreparedStatements(Integer maxOpenPreparedStatements) {
        this.maxOpenPreparedStatements = maxOpenPreparedStatements;
    }

    public Integer getMaxWait() {
        return maxWait;
    }
    public void setMaxWait(Integer maxWait) {
        this.maxWait = maxWait;
    }

    public Long getTimeBetweenEvictionRunsMillis() {
        return timeBetweenEvictionRunsMillis;
    }
    public void setTimeBetweenEvictionRunsMillis(Long timeBetweenEvictionRunsMillis) {
        this.timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis;
    }

    public Long getMinEvictableIdleTimeMillis() {
        return minEvictableIdleTimeMillis;
    }
    public void setMinEvictableIdleTimeMillis(Long minEvictableIdleTimeMillis) {
        this.minEvictableIdleTimeMillis = minEvictableIdleTimeMillis;
    }

    public String getValidationQuery() {
        return validationQuery;
    }
    public void setValidationQuery(String validationQuery) {
        this.validationQuery = validationQuery;
    }

    public Boolean getTestWhileIdle() {
        return testWhileIdle;
    }
    public void setTestWhileIdle(Boolean testWhileIdle) {
        this.testWhileIdle = testWhileIdle;
    }

    public Boolean getTestOnBorrow() {
        return testOnBorrow;
    }
    public void setTestOnBorrow(Boolean testOnBorrow) {
        this.testOnBorrow = testOnBorrow;
    }

    public Boolean getTestOnReturn() {
        return testOnReturn;
    }
    public void setTestOnReturn(Boolean testOnReturn) {
        this.testOnReturn = testOnReturn;
    }

    // 根据value返回枚举类型,主要在switch中使用
    public DbType getDBTypeByName(String typeName) {
        for (DbType dbType : dbTypes) {
            if (dbType.getTypeName().equalsIgnoreCase(typeName) ) {
                return dbType;
            }
        }
        return null;
    }

    public String  getDBClassNameByName(String typeName) {
        for (DbType dbType : dbTypes) {
            if (dbType.getTypeName().equalsIgnoreCase(typeName) ) {
                return dbType.getTypeName();//oracle/postgresql/mysql
            }
        }
        return null;
    }

    public String getJdbcDriverClassByTypeName(String dbTypename){
        String driverClassName="";
        for(int i=0; i< dbTypes.size(); i++){
            DbType dbtype = dbTypes.get(i);
            if(dbTypename.equalsIgnoreCase(dbtype.getTypeName().trim())){
                driverClassName = dbtype.getDriverClassName();
                break;
            }
        }
        return driverClassName.trim();
    }

    public Boolean checkExistByDbKey(String target_dbKey){
        Boolean isexit=false;
        for(int i=0; i< dbKeys.size(); i++){
            String dbKey = dbKeys.get(i);
            if(dbKey.equalsIgnoreCase(target_dbKey)){
                isexit= true;
                break;
            }
        }
        return isexit;
    }


    public JdbcConfig getJdbcCconfigByDbKey(String dbKey){
        JdbcConfig selectJdbcConfig=null;
        for(int i=0; i< jdbcConfigs.size(); i++){
            JdbcConfig jdbcConfig = jdbcConfigs.get(i);
            if(dbKey.equalsIgnoreCase(jdbcConfig.getDbkey())){
                selectJdbcConfig= jdbcConfig;
                break;
            }
        }
        return selectJdbcConfig;
    }



    @Bean
    public DynamicDataSource dynamicDataSource(Environment env, Log4jFilter log4jFilter) throws SQLException {

        Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
        for (int i = 0; i < this.jdbcConfigs.size(); i++) {
            JdbcConfig jdbcConfig = this.jdbcConfigs.get(i);
            DruidDataSource dataSource = new DruidDataSource();

            //注意这里的dbkey要唯一
            String dbKey = jdbcConfig.getDbkey().trim().toLowerCase();
            String dbTypeName = jdbcConfig.getDbtypename().trim().toLowerCase();
            //获取数据库驱动名称
            String jdbcDriverClassName = this.getJdbcDriverClassByTypeName(dbTypeName);

            //dataSource.setDriverClassName(env.getProperty("db.driver"));
            dataSource.setDriverClassName(jdbcDriverClassName);
            dataSource.setUrl(jdbcConfig.getDburl().trim());
            dataSource.setUsername(jdbcConfig.getUsername().trim());
            dataSource.setPassword(jdbcConfig.getPassword().trim());
            dataSource.setFilters(this.filters);
            dataSource.setMaxActive(this.maxActive);
            dataSource.setInitialSize(this.initialSize);
            dataSource.setMinIdle(this.minIdle);
            dataSource.setMaxWait(this.maxWait);
            dataSource.setTimeBetweenConnectErrorMillis(this.timeBetweenEvictionRunsMillis);
            dataSource.setMinEvictableIdleTimeMillis(this.minEvictableIdleTimeMillis);
            switch (dbTypeName) {
                case "oracle": {
                    //默认采用这个配置
                    dataSource.setValidationQuery(this.validationQuery);
                    break;
                }
                case "postgresql": {
                    dataSource.setValidationQuery("select version()");
                    break;
                }
                case "mysql": {
                    dataSource.setValidationQuery("select 1");
                    break;
                }
            }


            dataSource.setTestWhileIdle(this.testWhileIdle);
            dataSource.setTestOnBorrow(this.testOnBorrow);
            dataSource.setTestOnReturn(this.testOnReturn);
            dataSource.setPoolPreparedStatements(this.poolPreparedStatements);
            dataSource.setMaxOpenPreparedStatements(this.maxOpenPreparedStatements);
            dataSource.setDbType(dbTypeName);
            //dataSource.setDriverClassName(env.getProperty("db.jdbc.driverClassName"));
            dataSource.setDriverClassName(jdbcDriverClassName);
            dataSource.setProxyFilters(Arrays.asList(new Log4jFilter[]{log4jFilter}));

            //注意，很重要。数据库连接池的key，可以是实例名，也可以是实例名#用户名，这里实用的是实例名
            targetDataSources.put(dbKey, dataSource);
        }

        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        dynamicDataSource.setTargetDataSources(targetDataSources);

        return dynamicDataSource;
    }


    @Bean
    public Log4jFilter log4jFilter() {
        Log4jFilter filter = new Log4jFilter();
        filter.setStatementExecutableSqlLogEnable(true);
        return filter;
    }

//
//    @Bean(name = "sqlSessionFactoryBean")
//    public SqlSessionFactoryBean sqlSessionFactoryBean(DynamicDataSource dynamicDataSource) throws Exception {
//        SqlSessionFactoryBean sqlSessionFactory = new SqlSessionFactoryBean();
//        sqlSessionFactory.setDataSource(dynamicDataSource);
//        sqlSessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper*//*.xml"));
//        //sqlSessionFactory.setConfigLocation(new ClassPathResource("config/mybatis-config.xml"));
//
//        //MybatisSqlSessionFactoryBean sqlSessionFactory = new MybatisSqlSessionFactoryBean();
//        //sqlSessionFactory.setDataSource(dynamicDataSource);
//        //sqlSessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper*//*.xml"));
//        //sqlSessionFactory.setConfigLocation(new ClassPathResource("config/mybatis-config.xml"));
//
//
//        return sqlSessionFactory;
//    }

    @Bean(name = "sqlSessionFactory")
    public SqlSessionFactory sqlSessionFactory(DynamicDataSource dynamicDataSource, MybatisPlusInterceptor mybatisPlusInterceptor) throws Exception {
        //SqlSessionFactoryBean sqlSessionFactory = new SqlSessionFactoryBean();
        //sqlSessionFactory.setDataSource(dynamicDataSource);
        //sqlSessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper*//*.xml"));
        //sqlSessionFactory.setConfigLocation(new ClassPathResource("config/mybatis-config.xml"));

        MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();
        bean.setDataSource(dynamicDataSource);
        //设置拦截器
        bean.setPlugins(mybatisPlusInterceptor);

        bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper*//*.xml"));
        bean.setConfigLocation(new ClassPathResource("config/mybatis-config.xml"));
        //设置自动提交
        SqlSessionFactory sqlSessionFactory= bean.getObject();
        sqlSessionFactory.openSession(true);

        return sqlSessionFactory;
    }



    @Bean
    public DataSourceAdvice dataSourceAdvice() {
        return new DataSourceAdvice();
    }

    @Bean
    public PointcutAdvisor pointcutAdvisor(DataSourceAdvice dataSourceAdvice) {
        DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();
        advisor.setAdvice(dataSourceAdvice);
        AspectJExpressionPointcut expressionPointcut = new AspectJExpressionPointcut();
        expressionPointcut.setExpression("@annotation(cn.kmsoft.spatialboot.config.dbswitch.DynamicSwitch)");
        advisor.setPointcut(expressionPointcut);
        return advisor;
    }

    @Bean
    public ServletRegistrationBean druidServlet() {
        ServletRegistrationBean reg = new ServletRegistrationBean();

        reg.setServlet(new StatViewServlet());
        reg.addUrlMappings("/druid/*");
        reg.addInitParameter("loginUsername", "kmsoft");
        reg.addInitParameter("loginPassword", "kmsoft");
        reg.addInitParameter("logSlowSql", "true");
        return reg;
    }

    @Bean
    public FilterRegistrationBean filterRegistrationBean() {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();

        filterRegistrationBean.setFilter(new WebStatFilter());
        filterRegistrationBean.addUrlPatterns("/*");
        filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
        filterRegistrationBean.addInitParameter("profileEnable", "true");

        return filterRegistrationBean;
    }

    @Bean(name = "sqlSessionTemplate")
    public SqlSessionTemplate sqlSessionTemplate(@Qualifier("sqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }


}
